package dian.qing.li.demo.security.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.pactera.redis.utils.RedisUtils;
import dian.qing.li.demo.security.bean.UserParam;
import dian.qing.li.demo.security.config.security.LoginUser;
import dian.qing.li.demo.security.constants.Constants;
import dian.qing.li.demo.security.constants.DataType;
import dian.qing.li.demo.security.constants.RedisKey;
import dian.qing.li.demo.security.dto.*;
import dian.qing.li.demo.security.mapper.*;
import dian.qing.li.demo.security.service.UserService;
import dian.qing.li.demo.security.utils.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2020-07-01
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDto> implements UserService {

    @Autowired
    private RoleUserMapper roleUserMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private RoleAuthorityMapper roleAuthorityMapper;

    @Autowired
    private AuthorityMapper authorityMapper;

    @Autowired
    private ProjectUserPlanMapper projectUserPlanMapper;

    @Autowired
    private TagItemMapper tagItemMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public LoginUser login(String userCode) {
        Assert.isNotBlank(userCode, "[userCode]不能为空");
        String redisKey = RedisUtils.redisKey(RedisKey.LOGIN_USER, userCode.trim());
        if (RedisUtils.hasKey(redisKey)) {
            LoginUser loginUser = RedisUtils.get(redisKey, LoginUser.class);
            return loginUser;
        }
        UserDto user = new UserDto();
        user.setUserCode(userCode.trim());
        QueryWrapper<UserDto> queryUser = new QueryWrapper<>(user);
        UserDto userDto = this.getOne(queryUser);
        Assert.isNotNull(userDto, "账号或密码错误");
        LoginUser loginUser = new LoginUser();
        loginUser.setName(userDto.getUserName());
        loginUser.setUsername(userDto.getUserCode());
        loginUser.setPassword(userDto.getUserPassword());
        boolean expired = userDto.getExpiredTime() > -1 && userDto.getExpiredTime() < System.currentTimeMillis();
        boolean disabled = Constants.YES == userDto.getDisabled();
        loginUser.setEpirede(expired);
        loginUser.setDisabled(disabled);
        loginUser.setLocked(true);
        List<RoleDto> roleDtos = this.getRoles(userDto.getUserCode());
        Set<String> roles = roleDtos.stream().map(RoleDto::getRoleCode).collect(Collectors.toSet());
        roles.add(Constants.ANONYMOUS_USER);
        List<AuthorityDto> auths = this.getAuthorities(roleDtos);
        Set<String> authorities = auths.stream().map(AuthorityDto::getAuthCode).collect(Collectors.toSet());
        authorities.add(Constants.ANONYMOUS_USER);
        loginUser.setRoles(Lists.newArrayList(roles));
        loginUser.setAuthorities(Lists.newArrayList(authorities));
        RedisUtils.put(redisKey, loginUser);
        return loginUser;
    }

    @Override
    public void refreshRedis(String userCode, boolean deleted) {
        String redisKey = RedisUtils.redisKey(RedisKey.LOGIN_USER, userCode.trim());
        if (RedisUtils.hasKey(redisKey)) {
            RedisUtils.delete(redisKey);
        }
        if (!deleted) {
            this.login(userCode);
        }
    }

    @Override
    public Page<UserDto> pages(List<String> tags, UserParam param, Page page) {
        if (param == null) {
            param = new UserParam();
        }
        if (page == null) {
            page = new Page();
        }
        List<OrderItem> orders = page.getOrders();
        orders.stream().forEach(a -> a.setColumn(CodeUtils.underlineField(a.getColumn())));
        QueryBuilder builder = SqlUtils.builder(param);
        QueryWrapper query = builder.like("user_code", "user_name", "nick_name", "job_number", "mobile_phone", "email").query();
        if (CollectionUtils.isNotEmpty(tags)) {
            List<UserDto> userDtos = this.listByTags(tags);
            Set<String> userCodeSet = userDtos.stream().map(UserDto::getUserCode).collect(Collectors.toSet());
            if (CollectionUtils.isNotEmpty(userCodeSet)) {
                query.in("user_code", userCodeSet);
            } else {
                return new Page<>(page.getCurrent(), page.getSize());
            }
        }
        Page pageInfo = this.page(page, query);
        return pageInfo;
    }

    @Override
    public List<UserDto> listByTags(List<String> tags) {
        if (CollectionUtils.isEmpty(tags)) {
            return Lists.newArrayList();
        }
        Set<String> tagSet = tags.stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(tagSet)) {
            return Lists.newArrayList();
        }
        QueryWrapper<TagItemDto> queryItem = new QueryWrapper<>();
        queryItem.select("item_code");
        queryItem.eq("item_type", DataType.USER.getCode());
        queryItem.in("tag_code", tagSet);
        queryItem.groupBy("item_code");
        List<TagItemDto> itemDtos = tagItemMapper.selectList(queryItem);
        Set<String> userCodeSet = itemDtos.stream().map(TagItemDto::getItemCode).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(userCodeSet)) {
            return Lists.newArrayList();
        }
        QueryWrapper<UserDto> queryUser = new QueryWrapper<>();
        queryUser.in("user_code", userCodeSet);
        queryUser.orderByAsc("sort").orderByDesc("update_time");
        return this.list(queryUser);
    }

    @Override
    public List<UserDto> listByRole(String roleCode) {
        Assert.isNotBlank(roleCode, "[roleCode]不能为空");
        RoleDto findRole = new RoleDto();
        findRole.setRoleCode(roleCode.trim());
        QueryWrapper<RoleDto> queryRole = new QueryWrapper<>(findRole);
        RoleDto role = roleMapper.selectOne(queryRole);
        if (role == null) {
            return Lists.newArrayList();
        }
        QueryWrapper<RoleUserDto> queryRoleUser = new QueryWrapper<>();
        queryRoleUser.select("user_code");
        queryRoleUser.eq("role_code", role.getRoleCode());
        queryRoleUser.groupBy("user_code");
        List<RoleUserDto> roleUserDtos = roleUserMapper.selectList(queryRoleUser);
        Set<String> userCodeSet = roleUserDtos.stream().map(RoleUserDto::getUserCode).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(userCodeSet)) {
            return Lists.newArrayList();
        }
        QueryWrapper<UserDto> queryUser = new QueryWrapper<>();
        queryUser.in("user_code", userCodeSet);
        queryUser.orderByAsc("sort").orderByDesc("update_time");
        return this.list(queryUser);
    }

    @Override
    public UserDto find(Integer id) {
        Assert.isNotNull(id, "[id]不能为空");
        UserDto user = this.getById(id);
        Assert.isNotNull(user, StringUtils.join("不存在[", id, "]用户详情"));
        return user;
    }

    @Override
    public UserDto find(String userCode) {
        Assert.isNotBlank(userCode, "[userCode]不能为空");
        UserDto find = new UserDto();
        find.setUserCode(userCode.trim());
        QueryWrapper<UserDto> query = new QueryWrapper<>(find);
        UserDto user = this.getOne(query);
        Assert.isNotNull(user, StringUtils.join("不存在[", userCode, "]用户详情"));
        return user;
    }

    @Override
    public boolean inserted(UserDto inserted) {
        // 必填字段校验
        String[] check = {"userName", "areaCode", "nickName", "jobNumber", "mobilePhone", "email", "sex"};
        ObjUtils.isNotNull(inserted, true, check);
        // 字符串属性trim
        ObjUtils.trim(inserted);
        // 标准属性设置
        inserted.setId(null);
        inserted.setDeleted(null);
        inserted.setCreateBy(SysUtils.getUsername());
        inserted.setUpdateBy(inserted.getCreateBy());
        // 生成账号
        String userCode = this.createUserCode(inserted.getUserName());
        inserted.setUserCode(userCode);
        // 工号作为初始默认密码
        inserted.setUserPassword(passwordEncoder.encode(inserted.getJobNumber()));
        return this.save(inserted);
    }

    @Override
    public String createUserCode(String userName) {
        Assert.isNotBlank(userName, "[userName]不能为空");
        String userCode = CodeUtils.toPinyin(userName).trim();
        QueryWrapper<UserDto> query = new QueryWrapper<>();
        query.select("user_code");
        query.likeRight("user_code", userCode);
        List<UserDto> list = this.list(query);
        if (CollectionUtils.isEmpty(list)) {
            return userCode;
        }
        Integer max = list.stream().map(a -> {
            char[] chars = a.getUserCode().toCharArray();
            StringBuilder builder = new StringBuilder();
            for (int i = chars.length - 1; i >= 0; i--) {
                String num = String.valueOf(chars[i]);
                if (num.matches("\\d")) {
                    builder.append(num);
                }
            }
            return builder.length() > 0 ? Integer.valueOf(builder.reverse().toString()) : 0;
        }).max(Comparator.comparingInt(a -> a)).orElse(0);
        return StringUtils.join(userCode, max + 1);
    }

    @Override
    public boolean updated(UserDto updated) {
        Assert.isNotNull(updated, "[updated]不能为空");
        Assert.isNotNull(updated.getId(), "[updated.id]不能为空");
        // 字符串属性trim
        ObjUtils.trim(updated);
        // 设置不允许修改字段为空
        updated.setUserCode(null);
        updated.setUserPassword(null);
        updated.setDeleted(null);
        // 设置更新属性
        updated.setUpdateBy(SysUtils.getUsername());
        // 是否禁用，是否删除
        Integer disabled = updated.getDisabled();
        if (disabled != null) {
            if (Constants.YES == disabled) {
                updated.setDisabled(Constants.YES);
            } else {
                updated.setDisabled(Constants.NO);
            }
        }
        return this.updateById(updated);
    }

    @Override
    public boolean resetPassword(Integer id) {
        UserDto user = this.find(id);
        UserDto updated = new UserDto();
        updated.setId(id);
        updated.setUserPassword(passwordEncoder.encode(user.getJobNumber()));
        updated.setUpdateBy(SysUtils.getUsername());
        return this.updateById(updated);
    }

    @Override
    public boolean changePassword(String userCode, String oldPassword, String newPassword) {
        UserDto user = this.find(userCode);
        Assert.isTrue(Constants.NO == user.getDisabled(), "用户已禁用，不允许修改密码");
        Long expiredTime = user.getExpiredTime();
        if (expiredTime > 0) {
            Date expired = new Date(expiredTime);
            Assert.isFalse(expired.before(DateUtils.getCurrent()), "用户已过期，不允许修改密码");
        }
        Assert.isNotBlank(oldPassword, "旧密码不能为空，请重新输入");
        Assert.isNotBlank(newPassword, "新秘密不能为空，请重新输入");
        Assert.isFalse(StringUtils.equals(oldPassword, newPassword), "新旧密码不能相同，请重新输入");
        Assert.isTrue(passwordEncoder.matches(oldPassword, user.getUserPassword()), "旧密码错误，请重新输入");
        Assert.isTrue(this.checkPassword(user.getUserCode(), newPassword), "新密码不符合策略，请重新输入");
        UserDto updated = new UserDto();
        updated.setId(user.getId());
        updated.setUserPassword(passwordEncoder.encode(newPassword));
        updated.setUpdateBy(SysUtils.getUsername());
        return this.updateById(updated);
    }

    /**
     * 密码正则校验
     *
     * @param userCode 用户账号
     * @param password 密码
     * @author: liqingdian
     */
    private static boolean checkPassword(String userCode, String password) {
        // 密码不能包含账号
        if (password.toLowerCase().contains(userCode.toLowerCase())) {
            return false;
        }
        // 密码8-16个字符，至少1个大写字母，1个小写字母和1个数字
        Pattern p = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[\\s\\S]{8,16}$");
        Matcher m = p.matcher(password);
        return m.matches();
    }

    @Override
    public boolean disabled(Integer id, boolean disabled) {
        UserDto user = this.find(id);
        UserDto updated = new UserDto();
        updated.setId(id);
        updated.setUpdateBy(SysUtils.getUsername());
        int yes = disabled ? Constants.YES : Constants.NO;
        updated.setDisabled(yes);
        boolean success = true;
        if (yes != user.getDisabled()) {
            success = this.updateById(updated);
        }
        return success;
    }

    @Transactional
    @Override
    public boolean deleted(Integer id) {
        UserDto user = this.find(id);

        // 删除角色-用户关系
        UpdateWrapper<RoleUserDto> deleteRu = new UpdateWrapper<>();
        deleteRu.set(Constants.DELETE_FIELD, Constants.YES);
        deleteRu.eq("user_code", user.getUserCode());
        roleUserMapper.update(null, deleteRu);

        // 删除项目人员协调计划表
        UpdateWrapper<ProjectUserPlanDto> deletePup = new UpdateWrapper<>();
        deletePup.set(Constants.DELETE_FIELD, Constants.YES);
        deletePup.eq("user_code", user.getUserCode());
        projectUserPlanMapper.update(null, deletePup);

        // 删除标签
        UpdateWrapper<TagItemDto> deleteTag = new UpdateWrapper<>();
        deleteTag.set(Constants.DELETE_FIELD, Constants.YES);
        deleteTag.eq("item_code", user.getUserCode());
        deleteTag.eq("item_type", DataType.USER.getCode());
        tagItemMapper.update(null, deleteTag);

        UserDto updated = new UserDto();
        updated.setId(id);
        updated.setDeleted(Constants.YES);
        updated.setUpdateBy(SysUtils.getUsername());
        boolean success = this.updateById(updated);
        // 删除缓存
        this.refreshRedis(user.getUserCode(), true);
        return success;
    }

    @Transactional
    @Override
    public boolean grantRoles(String userCode, List<String> roleCodes) {
        if (CollectionUtils.isNotEmpty(roleCodes)) {
            UserDto user = this.find(userCode);
            Set<String> set = roleCodes.stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
            RoleUserDto inserted;
            for (String roleCode : set) {
                inserted = new RoleUserDto();
                inserted.setRoleCode(roleCode);
                inserted.setUserCode(user.getUserCode());
                roleUserMapper.insert(inserted);
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean cancelRoles(String userCode, List<String> roleCodes) {
        if (CollectionUtils.isNotEmpty(roleCodes)) {
            UserDto user = this.find(userCode);
            Set<String> set = roleCodes.stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
            if (CollectionUtils.isNotEmpty(set)) {
                UpdateWrapper<RoleUserDto> deleted = new UpdateWrapper<>();
                deleted.set(Constants.DELETE_FIELD, Constants.YES);
                deleted.eq("user_code", user.getUserCode());
                deleted.in("role_code", set);
                roleUserMapper.update(null, deleted);
                return true;
            }
        }
        return false;
    }

    @Transactional
    @Override
    public boolean makeTags(String userCode, List<String> tags) {
        UserDto user = this.find(userCode);
        Set<String> tagSet;
        if (tags == null) {
            tagSet = Sets.newHashSet();
        } else {
            tagSet = tags.stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        }
        TagItemDto tag = new TagItemDto();
        tag.setItemType(DataType.USER.getCode());
        tag.setItemCode(user.getUserCode());
        QueryWrapper<TagItemDto> queryTag = new QueryWrapper<>(tag);
        List<TagItemDto> tagItemDtos = tagItemMapper.selectList(queryTag);
        // 删除的
        List<Integer> deleteIds = new ArrayList<>();
        Set<String> exist = Sets.newHashSet();
        tagItemDtos.stream().forEach(a -> {
            String tagCode = a.getTagCode();
            if (tagSet.contains(tagCode)) {
                exist.add(tagCode);
            } else {
                deleteIds.add(a.getId());
            }
        });
        if (CollectionUtils.isNotEmpty(deleteIds)) {
            UpdateWrapper<TagItemDto> deleteTag = new UpdateWrapper<>();
            deleteTag.set(Constants.DELETE_FIELD, Constants.YES);
            deleteTag.in("id", deleteIds);
            tagItemMapper.update(null, deleteTag);
        }
        // 新增的
        tagSet.removeAll(exist);
        TagItemDto inserted;
        for (String tagCode : tagSet) {
            inserted = new TagItemDto();
            inserted.setItemType(DataType.USER.getCode());
            inserted.setItemCode(user.getUserCode());
            inserted.setTagCode(tagCode);
            tagItemMapper.insert(inserted);
        }
        return true;
    }

    /**
     * 获取角色列表
     *
     * @param userCode 用户名
     * @author: liqingdian
     */
    private List<RoleDto> getRoles(String userCode) {
        RoleUserDto roleUser = new RoleUserDto();
        roleUser.setUserCode(userCode);
        QueryWrapper<RoleUserDto> queryRu = new QueryWrapper<>(roleUser);
        queryRu.select("role_code");
        queryRu.groupBy("role_code");
        List<RoleUserDto> roleUserDtos = roleUserMapper.selectList(queryRu);
        Set<String> roleSet = roleUserDtos.stream().map(RoleUserDto::getRoleCode).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(roleSet)) {
            RoleDto role = new RoleDto();
            role.setDisabled(Constants.NO);
            QueryWrapper<RoleDto> queryRole = new QueryWrapper<>(role);
            queryRole.in("role_code", roleSet);
            return roleMapper.selectList(queryRole);
        }
        return Lists.newArrayList();
    }

    /**
     * 获取权限列表
     *
     * @param roleDtos 角色列表
     * @author: liqingdian
     */
    private List<AuthorityDto> getAuthorities(List<RoleDto> roleDtos) {
        if (CollectionUtils.isEmpty(roleDtos)) {
            return Lists.newArrayList();
        }
        boolean admin = roleDtos.stream().anyMatch(a -> Constants.ROLE_TYPE_ADMIN == a.getRoleType());
        AuthorityDto auth = new AuthorityDto();
        auth.setDisabled(Constants.NO);
        QueryWrapper<AuthorityDto> queryAuth = new QueryWrapper<>(auth);
        if (!admin) {
            Set<String> roleSet = roleDtos.stream().filter(a -> Constants.ROLE_TYPE_AUTH == a.getRoleType()).map(RoleDto::getRoleCode).collect(Collectors.toSet());
            QueryWrapper<RoleAuthorityDto> queryRa = new QueryWrapper<>();
            queryRa.select("auth_code");
            queryRa.in("role_code", roleSet);
            queryRa.groupBy("auth_code");
            List<RoleAuthorityDto> roleAuthorityDtos = roleAuthorityMapper.selectList(queryRa);
            Set<String> authSet = roleAuthorityDtos.stream().map(RoleAuthorityDto::getAuthCode).collect(Collectors.toSet());
            if (CollectionUtils.isEmpty(authSet)) {
                return Lists.newArrayList();
            }
            queryAuth.in("auth_code", authSet);
        }
        return authorityMapper.selectList(queryAuth);
    }
}
